FUNCTION DBPUT(*DESTINATION:DBWRKINDEX):DBERRTYPE*);
(*replace current item in workarea with the contents of DBMAIL*)
VAR DELTA,OLDLINKV,NEWLINKV,ISTACK:INTEGER;
  FP:FLDDESPTR;
  
  PROCEDURE PUTLINKF;
  (*replace current linked item with the item in DBMAIL*)
  BEGIN
    WITH WRKTABLE[DESTINATION] DO
      BEGIN (*replace the linked item*)
        WITH WIB^[TOS] DO
          BEGIN
            OLDLINKV:=LINKVALUE(WA,OFFSET);
            NEWLINKV:=ORD(DBMAIL.TXT[0]);
            IF DBMAIL.DBMAILTYPE = STRINGF THEN
              NEWLINKV:=NEWLINKV+1; (*link is 1 greater than
                                                string length*)
            DELTA:=NEWLINKV-OLDLINKV;
            IF DELTA > 0 THEN
              DBSHOWERR('DBPUT#1',MOVETAIL(DESTINATION,DELTA,OFFSET))
            ELSE
              DBSHOWERR('DBPUT#2',MOVETAIL(DESTINATION,DELTA,OFFSET-DELTA));
(*$R-*)
            MOVELEFT(DBMAIL.TXT,WA^[OFFSET],NEWLINKV);
            WA^[OFFSET]:=NEWLINKV;
(*$R+*)
          END (*WITH WIB*);
        (*now correct enclosing links also*)
        IF TOS > 0 THEN
          FIXLINKS(DESTINATION,(TOS-1),DELTA);
      END (*WITH WRKTABLE*);
  END (*PUTLINKF*);
  
  PROCEDURE PUTFIXEDF(FP:FLDDESPTR);
  (*replace a fixed width item in a record assumed already present*)
  CONST FIXEDWIDTH = 1;
  VAR SW:CRACKSWTYPE;
    FOFFSET:INTEGER;
  BEGIN
    WITH WRKTABLE[DESTINATION] DO
      WITH WIB^[TOS] DO
        WITH FP^ DO
          BEGIN
            SW.BL:=SWITCHES;
            IF NOT SW.A[FIXEDWIDTH] THEN
              DBPUT:=37 (*fixed width item expected*)
            ELSE
(*$R-*)
              WITH DBMAIL DO
                MOVELEFT(TXT, WA^[OFFSET+GETFOFFSET(DESTINATION)],
                                        MAXWIDTH);
(*$R+*)
          END (*WITH FP^*);
  END (*PUTFIXEDF*);
  
BEGIN (*DBPUT*)
  DBPUT:=0;
  TRACEWA(14,DESTINATION);
  IF DBTYPECHECK THEN
    WITH WRKTABLE[DESTINATION] DO
      WITH WIB^[TOS] DO
        WITH DBMAIL DO
          BEGIN
            IF DBMAILTYPE = GROUPF THEN
              BEGIN
                IF LEVEL <> GROUPT THEN
                  DBPUT:=36
                ELSE
                  PUTLINKF;
              END
            ELSE
              IF LEVEL <> FIELDT THEN
                DBPUT:=38
              ELSE
              IF (DESCRIPTORNUM >= 0) 
                  AND (DESCRIPTORNUM <= LASTFIELDDESCRIPTOR) THEN
                BEGIN (*it's a simple field*)
                  FP:=ACTIVEFIELDS[DESCRIPTORNUM];
                  IF FP = NIL THEN
                    DBPUT:=31 (*no such field initialized*)
                  ELSE
                    WITH FP^ DO
                      IF FLDTYPE <> DBMAILTYPE THEN
                        DBPUT:=36 (*mismatch*)
                      ELSE
                        IF DBMAILTYPE IN [STRINGF,INTEGERF,LONGINTF] THEN
                          CASE DBMAILTYPE OF
                            STRINGF: PUTLINKF;
                            LONGINTF,INTEGERF: PUTFIXEDF(FP)
                          END (*CASES*)
                        ELSE
                          DBPUT:=12; (*not yet implemented*)
                END (*simple field*)
              ELSE
                DBPUT:=31 (*no such field exists*);
          END (*WITH DBMAIL*)
  ELSE (*item assumed to be linked string*)
    PUTLINKF;
  TRACEWA(15,DESTINATION);
END (*DBPUT*);


(*SUPPORT PRIMITIVES*)
FUNCTION DBWRITEFIELD(*FID:TEXT; SOURCE:DBWRKINDEX):DBERRTYPE*);
(*access to Pascal's WRITE referring to the item currently pointed to
    in the source workarea; output is to file FID*)
VAR FP:FLDDESPTR;
  S:STRING[255];
  IA:REFLIST;
BEGIN
  DBWRITEFIELD:=0;
  WITH WRKTABLE[SOURCE] DO
    WITH WIB^[TOS] DO
      BEGIN
        IF LEVEL <> FIELDT THEN
          DBWRITEFIELD:=28 (*can't write out a whole group*)
        ELSE
          BEGIN
            FP:=ACTIVEFIELDS[DESCRIPTORNUM];
            IF FP=NIL THEN
              DBWRITEFIELD:=29
            ELSE
              WITH FP^ DO
                CASE FLDTYPE OF
                  GROUPF: DBWRITEFIELD:=28;
                  STRINGF:
                    BEGIN
(*$R-*)
                      MOVELEFT(WA^[OFFSET],S,LINKVALUE(WA,OFFSET));
(*$R+*)
                      DELETE(S,LENGTH(S),1); (*correct for link*)
                      WRITE(FID,S);
                    END;
                  INTEGERF:
                    BEGIN
(*$R-*)
                      MOVELEFT(WA^[OFFSET+GETFOFFSET(SOURCE)],IA[0],2);
(*$R+*)
                      WRITE(FID,IA[0]);
                    END;
                  BYTEF,LONGINTF,TEXTF: DBWRITEFIELD:=12; (*not implemented*)
                  ADDRCOUPLEF,SETF: DBWRITEFIELD:=30
                END (*CASE*);
          END (*LEVEL=FIELDT*);
      END (*WITH WIB*);
END (*DBWRITEFIELD*);

PROCEDURE DBGETDESCRIPTOR(*LEVEL:DBLEVELTYPE; 
                           DESCRIPTORNUM:INTEGER;
                           VAR PTR:FLDDESPTR)*);
(*used to pass descriptors to external programs. to avoid excessive
  interface symbol table, TRIX record is used to pass pointer as
  FLDDESPTR. external program is expected to declare its own records
  corresponding to RECORDT and GROUPT since they are not in the interface
  part*)
TYPE
  TRIXPTR=
    RECORD CASE DBLEVELTYPE OF
      FIELDT: (F:FLDDESPTR);
      RECORDT:(R:RECDESPTR);
      GROUPT: (G:GRPDESPTR)
    END;
VAR TP:TRIXPTR;
BEGIN
  IF DESCRIPTORNUM < 0 THEN
    TP.F := NIL
  ELSE
    CASE LEVEL OF
      FIELDT:  TP.F:=ACTIVEFIELDS[DESCRIPTORNUM];
      RECORDT: TP.R:=ACTIVERECORDS[DESCRIPTORNUM];
      GROUPT:  TP.G:=ACTIVEGROUPS[DESCRIPTORNUM]
    END (*CASES*);
  PTR:=TP.F;
END (*DBGETDESCRIPTOR*);

FUNCTION DBTAG(*NAME:STRING; SOURCE:DBWRKINDEX; VAR ITEMNUM:INTEGER):DBERRTYPE*);
(*search the current level for a descriptor corresponding to NAME*)
BEGIN
END (*DBTAG*);


(**WORKAREA PRIMITIVES*)
FUNCTION DBWRKOPEN(*WI:DBWRKINDEX; SIZE:INTEGER):DBERRTYPE*);
CONST WADELTA=64;
(*open a workarea for business*)
VAR I:INTEGER;
  P:WAPTR;
BEGIN
  DBWRKOPEN:=0;
  WITH WRKTABLE[WI] DO
    IF (SIZE <= 0) OR (SIZE > (PAGELASTBYTE+1)) THEN
      DBWRKOPEN:=2 (*size out of range*)
    ELSE
      IF (WA <> NIL) OR (WIB<>NIL) THEN
        DBWRKOPEN:=3 (*workarea already open*)
      ELSE
        IF NOT CHECKHEAP(SIZE+SIZEOF(WIBTYPE)) THEN
          DBWRKOPEN:=1 (*insufficient memory*)
        ELSE
          BEGIN  (*should be safe - do it*)
            NEW(WIB);
            NEW(WA); (*allocates WADELTA bytes - minimum wa size*)
            IF SIZE > WADELTA THEN
              I:=HEAPALLOCATE(SIZE-WADELTA); (*already checked for error*)
            WSIZE:=MAX(WADELTA,SIZE);
            ZEROWORKAREA(WI);
          END;
END (*DBWRKOPEN*);

FUNCTION DBWRKCLOSE(*WI:DBWRKINDEX):DBERRTYPE*);
BEGIN
END (*DBWRKCLOSE*);


(**FILE PRIMITIVES*)
FUNCTION DBFOPEN(*FNUM:DBFILENUM; TITLE:STRING):DBERRTYPE*);
BEGIN
  DBFOPEN:=0;
(*$I-*)
  CASE FNUM OF
    0: RESET(F0,TITLE);
    1: RESET(F1,TITLE);
    2: RESET(F2,TITLE);
    3: RESET(F3,TITLE);
    4: RESET(F4,TITLE)
  END (*CASE*);
  DBIORESULT:=IORESULT;
  IF DBIORESULT <> 0 THEN
    DBFOPEN:=23 (*unable to open file*)
  ELSE
    OPENFILES[FNUM]:=TRUE;
(*$I+*)
END (*DBFOPEN*);

FUNCTION DBFCLOSE(*FNUM:DBFILENUM):DBERRTYPE*);
BEGIN
  DBFCLOSE:=0;
(*$I-*)
  CASE FNUM OF
    0: CLOSE(F0);
    1: CLOSE(F1);
    2: CLOSE(F2);
    3: CLOSE(F3);
    4: CLOSE(F4)
  END (*CASE*);
  IF IORESULT <> 0 THEN
    DBFCLOSE:=26; (*unable to close file*)
(*$I+*)
END (*DBFCLOSE*);

FUNCTION DBFCREATE(*FNUM:DBFILENUM; WASCRATCH:DBWRKINDEX;
                                    SPEXTITLE,NEWTITLE:STRING):DBERRTYPE*);
(*open a new database file; lock it into directory; if there is a non-empty
  specification file fitle, copy the spex into the new file. uses wascratch
  to initialize the file.  assumes wascratch will be associated with fnum
  file*)
VAR RSLT:INTEGER;

  PROCEDURE BLANKZEROPAGE(VAR F:FILETYPE);
  VAR BLOCKCOUNT:INTEGER;
  BEGIN
    BLOCKCOUNT:=(PAGELASTBYTE+1) DIV 512;
    RSLT:=BLOCKWRITE(F,WRKTABLE[WASCRATCH].WA^,BLOCKCOUNT,0);
    DBFCREATE:=0;
    IF RSLT <> BLOCKCOUNT THEN
      DBFCREATE:=9
    ELSE
(*$I-*)
      BEGIN
        CLOSE(F,LOCK);
        IF IORESULT <> 0 THEN
          DBFCREATE:=10  (*unable to lock file*)
        ELSE
          BEGIN
            RESET(F,NEWTITLE);
            IF IORESULT <> 0 THEN
              DBFCREATE:=11 (*unable to re-open the file*)
            ELSE
              OPENFILES[FNUM]:=TRUE;
          END;
      END (*RSLT = BLOCKCOUNT*);
  END (*BLANKZEROPAGE*);

BEGIN (*DBFCREATE*)
  RSLT:=CHECKWORKAREA(WASCRATCH,(PAGELASTBYTE+1));
  IF RSLT<>0 THEN
    DBFCREATE:=RSLT (*pass on error from checkworkarea*)
  ELSE
    IF OPENFILES[FNUM] THEN
      DBFCREATE:=5 (*file already open and in use*)
    ELSE
      IF LENGTH(NEWTITLE) = 0 THEN
        DBFCREATE:=6 (*requires non-nul title string*)
  ELSE
(*$I-*)
    BEGIN
      CASE FNUM OF
        0: RESET(F0,NEWTITLE);
        1: RESET(F1,NEWTITLE);
        2: RESET(F2,NEWTITLE);
        3: RESET(F3,NEWTITLE);
        4: RESET(F4,NEWTITLE)
      END (*CASE*);
      RSLT:=IORESULT;
  (*$I+*)
      IF RSLT=0 THEN (*file already on disk*)
        DBFCREATE:=4
      ELSE
        IF RSLT = 12 THEN (*file already open, but not caught above*)
          DBFCREATE:=99 (*system error*)
    ELSE
      BEGIN
  (*$I-*)
        CASE FNUM OF
          0: REWRITE(F0,NEWTITLE);
          1: REWRITE(F1,NEWTITLE);
          2: REWRITE(F2,NEWTITLE);
          3: REWRITE(F3,NEWTITLE);
          4: REWRITE(F4,NEWTITLE)
        END (*CASE*);
        RSLT:=IORESULT;
  (*$I+*)
        IF RSLT <> 0 THEN
          DBFCREATE:=7 (*rewrite failure*)
        ELSE
          IF LENGTH(SPEXTITLE) = 0 THEN
            BEGIN  (*ok to create the file now*)
              ZEROWORKAREA(WASCRATCH);
              CASE FNUM OF
                0: BLANKZEROPAGE(F0);
                1: BLANKZEROPAGE(F1);
                2: BLANKZEROPAGE(F2);
                3: BLANKZEROPAGE(F3);
                4: BLANKZEROPAGE(F4)
              END (*CASE*);
            END (*LENGTH(SPEXTITLE) = 0*)
          ELSE
            DBFCREATE:=12; (*spexfile transfer not yet implemented*)
      END (*RSLT <> 12*);
    END (*LENGTH(NEWTITLE) <> 0*);
END (*DBFCREATE*);

FUNCTION DBFREMOVE(*FNUM:DBFILENUM):DBERRTYPE*);
BEGIN
  DBFREMOVE:=0;
(*$I-*)
  CASE FNUM OF
    0: CLOSE(F0,PURGE);
    1: CLOSE(F1,PURGE);
    2: CLOSE(F2,PURGE);
    3: CLOSE(F3,PURGE);
    4: CLOSE(F4,PURGE)
  END (*CASE*);
  IF IORESULT <> 0 THEN
    DBFREMOVE:=22
  ELSE
    OPENFILES[FNUM]:=FALSE;
(*$I+*)
END (*DBFREMOVE*);

FUNCTION DBGETPAGE(*FNUM:DBFILENUM; DESTINATION:DBWRKINDEX;
                                   PAGENUM:INTEGER):DBERRTYPE*);
VAR BLOCKSMOVED,BLOCKSINPAGE,LINKV,LX,DUMMY:INTEGER;

  PROCEDURE MOVEWA(VAR F:FILETYPE);
  BEGIN
    BLOCKSMOVED:=BLOCKREAD(F,WRKTABLE[DESTINATION].WA^,
                                    BLOCKSINPAGE, (PAGENUM*BLOCKSINPAGE));
  END;

BEGIN
  DBGETPAGE:=DBHOME(DESTINATION);
  BLOCKSINPAGE:=(PAGELASTBYTE+1) DIV 512;
  WITH WRKTABLE[DESTINATION] DO
    CASE FNUM OF
      0: MOVEWA(F0);
      1: MOVEWA(F1);
      2: MOVEWA(F2);
      3: MOVEWA(F3);
      4: MOVEWA(F4)
    END (*CASE*);
  IF BLOCKSMOVED <> BLOCKSINPAGE THEN
    DBGETPAGE:=25
  ELSE
    WITH WRKTABLE[DESTINATION] DO
      BEGIN
        (*get SPACEINUSE by following links to end*)
        LX:=0;
        LINKV:=LINKVALUE(WA,0);
        WHILE LINKV<>0 DO
          BEGIN
            LX:=LX+LINKV;
            LINKV:=LINKVALUE(WA,LX);
          END;
        SPACEINUSE:=LX+1;
        WITH WIB^[0] DO
          BEGIN
            LINKV:=LINKVALUE(WA,0);
            DESCRIPTORNUM:=LINKVALUE(WA,LINKSIZE(LINKV)); (*tag*)
          END;
      END (*WITH WRKTABLE*);
END (*DBGETPAGE*);

FUNCTION DBPUTPAGE(*FNUM:DBFILENUM; SOURCE:DBWRKINDEX;
                                   PAGENUM:INTEGER):DBERRTYPE*);
VAR BLOCKSMOVED,BLOCKSINPAGE:INTEGER;

  PROCEDURE MOVEWA(VAR F:FILETYPE);
  BEGIN
    BLOCKSMOVED:=BLOCKWRITE(F,WRKTABLE[SOURCE].WA^,
                          BLOCKSINPAGE, (PAGENUM*BLOCKSINPAGE));
  END;

BEGIN
  DBPUTPAGE:=0;
  BLOCKSINPAGE:=(PAGELASTBYTE+1) DIV 512;
  WITH WRKTABLE[SOURCE] DO
    CASE FNUM OF
      0: MOVEWA(F0);
      1: MOVEWA(F1);
      2: MOVEWA(F2);
      3: MOVEWA(F3);
      4: MOVEWA(F4)
    END (*CASE*);
  IF BLOCKSMOVED <> BLOCKSINPAGE THEN
    DBPUTPAGE:=24;
END (*DBPUTPAGE*);


(**DESCRIPTOR INITIALIZING PRIMITIVES*)
FUNCTION DBGROUPINIT(*FNUM:DBFILENUM; VAR GROUPNUM:INTEGER;
                     GROUPNAME:STRING):DBERRTYPE*);
(*load the descriptor lists from groups 1,2,3 of the database using
  workarea 0 as temporary store. note: these groups may extend over
  more than one page*)
CONST
  WA0=0; (*work area #0*)
VAR GN,LINKV,PAGENUM,DUMMY:INTEGER;

  PROCEDURE LOADDESCRIPTORS(LVL:DBLEVELTYPE);
  VAR GPTR:GRPDESPTR;
    RPTR:RECDESPTR;
    FPTR:FLDDESPTR;
  BEGIN
    WITH WRKTABLE[WA0] DO
      WITH WIB^[TOS] DO
        BEGIN
          GN:=0;
          LINKV:=LINKVALUE(WA,OFFSET);
          WHILE LINKV > 2 (*ignore empty dummy records*) DO
            BEGIN
              CASE LVL OF
                GROUPT:
                  BEGIN
                    NEW(GPTR);
                    DBSHOWERR('GROUPINIT(G)',
                              HEAPALLOCATE(LINKV-SIZEOF(GRPDESCRIPTOR)));
      (*$R-*)
                    MOVELEFT(WA^[OFFSET],GPTR^,LINKV);
      (*$R+*)
                    ACTIVEGROUPS[GN]:=GPTR;
                  END (*GROUPT*);
                RECORDT:
                  BEGIN
                    NEW(RPTR);
                    DBSHOWERR('GROUPINIT(R)',
                              HEAPALLOCATE(LINKV-SIZEOF(RECDESCRIPTOR)));
      (*$R-*)
                    MOVELEFT(WA^[OFFSET],RPTR^,LINKV);
      (*$R+*)
                    ACTIVERECORDS[GN]:=RPTR;
                  END (*RECORDT*);
                FIELDT:
                  BEGIN
                    NEW(FPTR);
                    DBSHOWERR('GROUPINIT(F)',
                              HEAPALLOCATE(LINKV-SIZEOF(FLDDESCRIPTOR)));
      (*$R-*)
                    MOVELEFT(WA^[OFFSET],FPTR^,LINKV);
      (*$R+*)
                    ACTIVEFIELDS[GN]:=FPTR;
                  END (*FIELDT*)
              END (*CASE*);
              DUMMY:=DBNEXT(WA0);
              LINKV:=LINKVALUE(WA,OFFSET);
              IF LINKV <> 0 THEN GN:=GN+1;
            END (*WHILE*);
        END (*WITH*);
  END (*LOADDESCRIPTORS*);
  
  PROCEDURE NEWPAGE;
  BEGIN
    PAGENUM:=PAGENUM+1;
    DBSHOWERR('GROUPINIT#2',DBGETPAGE(FNUM,WA0,PAGENUM));
  END (*NEWPAGE*);
                     
BEGIN (*DBGROUPINIT*)
  DBGROUPINIT:=0;
  (*initially load all descriptors - selection to be added later*)
  IF GROUPNAME <> 'ALL' THEN
    DBGROUPINIT:=12;
  (*loads descriptor groups into WA0*)
  PAGENUM:=-1;
  NEWPAGE;
  SPECIALGROUPPAGE[1]:=PAGENUM;
  DUMMY:=DBHOME(WA0);
  DUMMY:=DBNEXT(WA0); (*go to head of group descriptor list*)
  DUMMY:=DBDESCEND(WA0); (*head of 1st record*)
  WITH WRKTABLE[WA0] DO
    WITH WIB^[TOS] DO
      BEGIN
        LOADDESCRIPTORS(GROUPT);
        GROUPNUM:=GN;
        (*now load record descriptors*)
        DUMMY:=DBHOME(WA0);
        IF DBSEEK(WA0,2(*RD'S*)) <> 0 THEN NEWPAGE;
        SPECIALGROUPPAGE[2]:=PAGENUM;
        DUMMY:=DBDESCEND(WA0);
        LOADDESCRIPTORS(RECORDT);
        (*now fields*)
        DUMMY:=DBHOME(WA0);
        IF DBSEEK(WA0,3(*FD'S*)) <> 0 THEN NEWPAGE;
        SPECIALGROUPPAGE[3]:=PAGENUM;
        DUMMY:=DBDESCEND(WA0);
        LOADDESCRIPTORS(FIELDT);
      END (*WITH WIB*);
END (*DBGROUPINIT*);

FUNCTION DBGROUPRELEASE(*GROUPNUM:INTEGER):DBERRTYPE*);
(*de-allocate storage for the designated group descriptors, and
  their dependent record and field descriptors*)
BEGIN
END (*DBGROUPRELEASE*);


(**INITIALIZATION*)
PROCEDURE DBINITIALIZE;
VAR WI:INTEGER;
BEGIN
  FOR WI:=0 TO LASTFILENUM DO OPENFILES[WI]:=FALSE;
  FOR WI:=0 TO LASTWRKINDEX DO
    WITH WRKTABLE[WI] DO
      BEGIN
        TOS:=0;
        WIB:=NIL;
        WSIZE:=0;
        SPACEINUSE:=0;
        WA:=NIL;
      END;
  FOR WI:=0 TO LASTSPECIALGROUP DO SPECIALGROUPPAGE[WI]:=0;
  FOR WI:=0 TO LASTGROUPDESCRIPTOR DO ACTIVEGROUPS[WI]:=NIL;
  FOR WI:=0 TO LASTRECDESCRIPTOR DO ACTIVERECORDS[WI]:=NIL;
  FOR WI:=0 TO LASTFIELDDESCRIPTOR DO ACTIVEFIELDS[WI]:=NIL;
  MARK(HEAPMARKER);
  WI:=DBWRKOPEN(0,(PAGELASTBYTE+1)); (*open wa # 0 for full page*)
  DBTYPECHECK:=TRUE;
  
  (*following lines are for debugging*)
  DEBUGGING:=FALSE;
  DBTRACESET:=[ ];
  TRACELB:=0;
  TRACEUB:=99;
END (*DBINITIALIZE*);


(**ORDERLY TERMINATION*)
FUNCTION DBCLOSEDOWN(*:DBERRTYPE*);
BEGIN
END (*DBCLOSEDOWN*);



END. (*END OF DBUNIT*)

